<?php

/*
 * 数据库操作基础类
 *
 * author: levsion	<levsion@163.com>
 * created: 7/22/2010
 *
 */

/* 
 * 调试开关, 在包含此文件之外可以定义是否打开调试模式
 *
 * 调试模式下会记录程序执行过程中的信息, 最后调用 $db->dump() 可以显示这些信息
 *
 */
defined('DATABASE_DEBUG') || define('DATABASE_DEBUG', false);

/* 
 * 日志开关, 记录错误 SQL 语句到日志文件中 
 *
 * 如在包含此文件前 define('DATABASE_ERROR_LOG', '/tmp/db.error.log')
 * 那么在所有请求中出错的SQL语句都会被记录到此文件中
 *
 * 如果不需要记录日志, 则在包含些文件前 define('DATABASE_ERROR_LOG', false)
 *
 */
defined('DATABASE_ERROR_LOG') || define('DATABASE_ERROR_LOG', dirname(__FILE__).'/db.error.'.date('md').'.log');

/* 
 * 记录操作时间大于某个时间的 SQL 语句 
 *
 * 如在包含此文件前 define('DATABASE_LOG_SQL_TIME', 0.1)
 * 那么在所有请求中如果执行时间超过 0.1 秒的 SQL 都会被记录到日志文件中
 *
 * 此功能依赖于 DATABASE_ERROR_LOG 常量, 在 DATABASE_DEBUG 模式下无效
 *
 */
defined('DATABASE_LOG_SQL_TIME') || define('DATABASE_LOG_SQL_TIME', 0);

/*
 * 默认 mysql 连接超时时间设置为5秒连接不上就认为超时
 *
 * 如在包含此文件前 define('DATABASE_DEFAULT_TIMEOUT', 3);
 * 那么在所有请求中3秒连接不上mysql就算是超时
 *
 */
defined('DATABASE_DEFAULT_TIMEOUT') || define('DATABASE_DEFAULT_TIMEOUT', 5);

defined('LOG_PRI_DEBUG') || define('LOG_PRI_DEBUG',    128);
defined('LOG_PRI_ERROR') || define('LOG_PRI_ERROR',      8); 

/*
 *
 * 全部函数原型
 *
 * <prototype>
 *		static function &getInstance($tag_name) // 获取 Database 单实例
 *		function &__construct($config_callback_function = null, $autoload_callback_config_filename = null) {
 *		function __destruct()
 *		function setTagName($tag_name) 
 *		function setAutoClose($switch = true) // 控制每次query()后自动关闭mysql连接的开关
 *		function setConfigFunction($callback_function, $filename = null) // 设置获取mysql host, user, pass的回调函数
 *		function load($const_table, $hash = null, $need_hash = true) // 获取某张表在哪台db上
 *		function lastError()
 *		function connect()
 *		function setReverseOrder($limit, $asc, $desc)
 *		function &queryWithPage($sql, $current_page = 1, $total_rows = 0, $page_size = 25) // 翻页列表函数
 *		function &query($sql, $query_with_page = false) // 普通请求, 返回mysql资源连接
 *		function queryWithReturnInsertId($sql) // 执行插入语句, 并返回mysql自增ID
 *		function &getRow($sql)
 *		function &getRows($sql)
 *		function &getValue($sql)
 *		function getLogs() {
 *		function getPage()
 *		function affectedRows()
 *		function close()
 *		function escape($string)
 *		function dump($ret = false)
 *		protected function logDebug($message, $level = 'info')
 *		protected function logError($message, $level = 'error')
 *		protected function setLastError($message)
 * </prototype>
 *
 * 在此文件最下面有db配置文件函数的示例, 请参照改写
 *
 * 示例代码, 备注, 此类库只提供基本操作, 没涉及到安全检查之类的
 *
 * <code>
 *		$user = "oxff";
 *
 *		$db = new Database;
 *		$db->setConfigFunction("example_callback_function", "./example.php"); // 设置使用db配置函数
 *		$db->setConfigFunction("example_callback_function"); // 功能同上同样设置配置函数
 *		$db->load("table_user", $user);			// 第二步载入 table_user 表相关的配置
 *		
 *		// 获取一行数据
 *		$row = $db->getRow("SELECT * FROM table_user WHERE username = $user LIMIT 1"); 
 *
 *		// 获取年龄大于20岁的用户30个
 *		$rows= $db->getRows("SELECT * FROM table_user WHERE age > 20 LIMIT 30"); 
 *
 *		// 获取这个用户的年龄字段的值
 *		$age = $db->getValue("SELECT age FROM table_user WHERE username = $user LIMIT 1"); 
 *
 *		// 翻页列表, 每页显示 50 条, (总数由此模块自动查出来)
 *		$page_list = $db->queryWithPage("SELECT * FROM table_user", $_GET["page"], 0, 50);
 *
 *		// 翻页列表, 总数从cache中取到, 每页30条, (总数从cache中取)
 *		$diary_count = $memcache->get($user."_total_diary_count");
 *		$page_list = $db->queryWithPage("SELECT * FROM table_user", $_GET["page"], $diary_count, 30);
 *
 *		// 关掉每次查询后自动关闭mysql连接的特性
 *      $db->setAutoClose(false);
 *      $db->query($sql);
 *      ....
 *      $db->getRows($sql);
 *      ...
 *      // 上面关掉了自动关闭链接, 所以最后需要手动关掉
 *      $db->close();
 *
 *		// 显示上面的调试信息
 *		$db->dump();
 *
 *		// 在不同的文件中使用同一个实例
 *		a.php 中
 *		$db = Database::getInstance("abc");  // 获取一个标签为 abc 的实例
 *		...
 *
 *		b.php 中
 *		$db = Database::getInstance("abc");  // 此实例为 a.php 中使用的实例
 *		...
 *
 *		c.php 中
 *		$db = Database::getInstance("abc");  // 同上
 *
 *		d.php 
 *		$db = Database::getInstance("qun");  // 重新获取一个实例, 标识为 qun 使用, 此实例为一个全新的object
 *
 *		$a = new Database;
 *		$a->setTagName("home"); // 将此实例标签为 home, 以便在其他模块中获取这个实例
 *
 * </code>
 *
 */

final class Database {

	private static $instances;				// 单实例
	
    private $_config;					    // 通过 config_callback_function() 取到的db配置信息
    
	protected $config_callback_function;	// 用户自定义的db配置函数
    protected $connection;					// mysql connection
    protected $hash_str;					// 拆库,表的key, 通常都是用户名
    protected $last_error;					// 最后一次执行操作的错误信息, 没有错误为null
    protected $logs;						// 调试信息, DATABASE_DEBUG 打开时才有值
	protected $page;						// 翻页查询时包含的相关翻页信息,总记录数,当前第几页..
	protected $affected_rows = 0;			// 最后一次操作时所影响的数据条数
	protected $auto_close_mysql_link = true;// 每次执行查询后都自动关闭mysql连接
	protected $external_logger = null;		// 定义外部处理错误的对象,此class必须包含一个log()函数

	/* 
	 * 获取单 database 类的实例
	 *
	 * <code>
	 *		$db = Database::getInstance("example"); // 获取一个database实例, 并将此实例标识为 example 频道使用
	 * </code>
	 *
	 * @param string $tag_name	为些实例打标签, 以后所有些标签都将返回同一个实例
	 * @return Database object
	 *
	 */
	static function &getInstance($tag_name) {

		if ($tag_name && isset(self::$instances[$tag_name])) {
			return self::$instances[$tag_name];
		}

		$db = new Database;
		if ($tag_name) {
			$db->setTagName($tag_name);
		}

		return $db;
	}

	/*
	 * 构造函数
	 *
	 * @param string $config_callback_function				获取 db 配置的回调函数, 传递函数名
	 * @param string $autoload_callback_config_filename		自动加载回调函数的文件, 绝对或相对路径
	 * @return object
	 *
	 */
    function &__construct($config_callback_function = 'bierenshuo_get_db_config', $autoload_callback_config_filename = null) {

		if ($config_callback_function || $autoload_callback_config_filename) {
			$this->setConfigFunction($config_callback_function, $autoload_callback_config_filename);
		}
        return $this;
    }

    function __destruct() {
        is_resource($this->connection) && mysql_close($this->connection);
    }

	/*
	 * 给当前实例打标签
	 *
	 * @param string $tag_name  标签名
	 * @return true / false
	 *
	 */
	function setTagName($tag_name) {

		if (!isset(self::$instances[$tag_name])) {
			self::$instalces[$tag_name] = $this;
			return true;
		}
		return false;
	}

	/*
	 * 设置外部日志处理函数
	 *
	 * @param object $obj		外部处理错误信息的对像, 此对象必须包含一个 log() 函数
	 * @return true or false;
	 *
	 */
	function setExternalLoger($obj) {

		if (is_object($obj) && method_exists($obj, "log")) {
			$this->external_logger = $obj;
			return true;
		}
		return false;
	}

	/*
	 * 设置是否自动关闭 mysql 连接
	 *
	 * @param bool $switch	开关
	 * @return void
	 *
	 */
	function setAutoClose($switch = true) {
		$this->auto_close_mysql_link = $switch;
	}

	/*
	 * 函数功能: 设置并载入 db 配置文件
	 *
	 * 详细配置写法见 core/pub/config/database/example.cfg.php 文件
	 *
	 * <code>
	 *		$db = new Database;
	 *		if (false == $db->setConfigFunction("xxx_db_callback_function", "/path/to/path/example.cfg.php")) {
	 *			echo $db->lastError();
	 *			exit;
	 *		}
	 * </code>
	 *
	 * @param string $callbacl_function	获取db信息的回调函数名
	 * @param string $filename			要载入的配置文件名
	 * @return true / false
	 *
	 */
	function setConfigFunction($callback_function, $filename = null) {

		if ($filename) {
			if (!is_file($filename)) {
				$this->setLastError("can not found config file: $filename");
				return false;
			}
			if (!include_once($filename)) {
				$this->setLastError("can not load config file: $filename");
				return false;
			}
		}

		if ($callback_function) {
			if (!function_exists($callback_function)) {
				$this->setLastError("can not found callback function: $callback_function");
				return false;
			}
		}

		$this->config_callback_function = $callback_function;
		return true;

	}

	/*
	 * 载入配置块, 标识后面的 SQL 将要使用什么host, db, table
	 *
	 * <code>
	 *		$db = new Database;
	 *		$db->setConfigFunction("example_db_callback_function", "./example.php");
	 *		$db->load("table_user", $_SESSION["Account"]);
	 *		$db->query($sql);
	 *		$db->load("table_user_fav", $_SESSION["Account"]);
	 *		$db->query($sql);
	 * </code>
	 *
	 * @param string $const_table	已经在配置文件中配置好的case块区名
	 * @param string $hash			拆库的 hash, 通常是用户名
	 * @param bool   $need_hash		是否需要自动进行 md5($hash) 操作, 台脚本需指定 hash 时将此值置false
	 * @return true or false
	 *
	 */
    function load($const_table, $hash = null, $need_hash = true) {

		if (DATABASE_DEBUG) {
			$this->logDebug("[T] Load($const_table, $hash, $need_hash)");
		}

		if (!$this->auto_close_mysql_link) {
			$this->close();
		}
		$this->hash_str = $hash;

		if ($const_table && $this->config_callback_function) {
			$callback_function = $this->config_callback_function;
			if ($this->_config = $callback_function($const_table, $hash, $need_hash)) {
				return true;
			}
		}

		return false;
    }

	/*
	 * 获取最后一次操作的出错信息, 如果最后一次没有错误, 则此值为空
	 *
	 * <code>
	 *		$res = $db->query($sql);
	 *		if (false == $res) {
	 *			echo $db->lastError();
	 *		}
	 * </code>
	 * @param void
	 * @return string
	 *
	 */
    function lastError() {
        return $this->last_error;
    }

	/*
	 * 连接 mysql 数据库
	 *
	 * <code>
	 *		$db = new Database;
	 *		$db->setConfigFunction("example_get_db_config");
	 *		$db->load("table_user", $_SESSION["Account"]);
	 *		$conn = $db->connect();
	 * </code>
	 *
	 * @param void
	 * @return false or mysql connection
	 *
	 */
    function connect() {

		if (is_resource($this->connection)) { 
			return $this->connection; 
		}

        if (empty($this->_config)) {
            $this->setLastError('Lost data source');
			return false;
        }

		ini_set('mysql.connect_timeout', DATABASE_DEFAULT_TIMEOUT);
		if (DATABASE_DEBUG) {
			$time_start   = microtime(1);
			$this->connection = @mysql_connect($this->_config['host'], $this->_config['user'], $this->_config['pass']);
			$this->logDebug("[T] connect time:".(microtime(1) - $time_start));
		} else {
			$this->connection = @mysql_connect($this->_config['host'], $this->_config['user'], $this->_config['pass']);
		}

        if (!$this->connection) {
			$this->setLastError(mysql_error());
            return;
        }

        if (isset($this->_config['database'])) {
            if (!mysql_select_db($this->_config['database']) && DATABASE_DEBUG) {
                $this->logDebug("[E] ".mysql_error($this->connection));
            }
        }

		return $this->connection;
    }

	/* 
	 * 设置反向排序的参数
	 *
	 * <code>
	 *		$db = new Database;
	 *		$db->setConfigFunction("example_get_db_config");
	 *		$db->load("table_user", $_SESSION["Account"]);
	 *		$db->setReverseOrder(50000, "auto_id ASC", "auto_id DESC"); // 五万条之前正常排序, 五万条之后反向排序
	 *		$db->queryWithPage($sql);
	 * </code>
	 *
	 * @param integer $limit	达到多少条记录后开始反转翻页
	 * @param string  $asc		正向排序方式
	 * @param string  $desc		反向排序方式
	 * @return void
	 *
     */
    function setReverseOrder($limit, $asc, $desc) {
        $this->page['reverse_limit']    = intval($limit);
        $this->page['reverse_asc']      = $asc;
        $this->page['reverse_desc']     = $desc;
    }

	/* 
	 * 带翻页的查询, 通常在展示数据列表的时候调用此函数, 没有数据返回 null, 出错返回 false
	 *
	 * 第三个参数 $total_rows 是从外部传递此查询总共有多少条记录
	 * 这个参数可以在别的地方被缓存, 翻页的时候用缓存值能有效的减轻db上 SELECT COUNT(*) 的压力
	 *
	 * @param string	$sql			查询语句
	 * @param integer	$current_page	当前显示第几页
	 * @param integer	$total_rows		此查询总共有多少条记录
	 * @param integer	$page_size		每页显示多少条
	 * @return null, false or array()
	 *
	 */
    function &queryWithPage($sql, $current_page = 1, $total_rows = 0, $page_size = 25) {

        $this->page['size']         = (int)$page_size;
        $this->page['current']      = (int)$current_page;
        $this->page['total_rows']   = (int)$total_rows;
        $this->page['total_pages']  = 1;

        $ret = null;
        $res = $this->query($sql, true);

        if ($this->affected_rows) {

            $ret = array();
			while($row = mysql_fetch_assoc($res)) { 
				$ret["rows"][] = $row; 
			}
            mysql_free_result($res);
            $ret["page"] = $this->page;

		} else if (false === $res) {

			$ret = $res;
		}

        if ($ret && isset($this->page['reverse_limit'])) {
			/*
			 * 优化反向排序时数据展示的方式, db中最后一条数据排在页面展示的最下面一条
			 */
            if ($this->page['reverse_limit'] < $this->page['total_rows'] && $this->page['current']>floor($this->page["total_pages"]/2)) {
				$cnt = sizeof($ret["rows"]);
				for ($i = $cnt - 1; $i >= 0; $i--) {
					$new_rows[] = $ret["rows"][i];
				}
				$ret["rows"] = $new_rows;
            }
        }

        return $ret;
    }

	/*
	 * 向数据库发送操作请求
	 *
	 * 如果是翻页查询请调用 $db->queryWithPage() 函数, 第二个参数为内部调用才传值
	 *
	 * <code>
	 *		$res = $db->query($sql);
	 *		if ($res) {
	 *			while ($row = mysql_fetch_assoc($res)) {
	 *				print_r($row);
	 *			}
	 *		}
	 * </code>
	 *
	 * @param string $sql				要操作的 SQL 语句
	 * @param bool   $query_with_page	是否是进行翻页查询
	 * @return false or true or mysql resultset
	 *
	 */
	function &query($sql, $query_with_page = false) {

		$result                 = false;
        $this->last_error       = null;
        $this->affected_rows    = 0;

		if (!is_resource($this->connection) && !$this->connect()) {
			return $result;
		}
        
        if (!empty($this->_config['table_alias'])) {
            if (DATABASE_DEBUG) {
				$this->logDebug("[S] $sql");
                $sql = preg_replace('/([from|update|into]\s+)'.$this->_config['table_alias'].' ?/is', '\1'.$this->_config['table'].' ', $sql, 1);
            } else {
                $sql = preg_replace('/([from|update|into]\s+)'.$this->_config['table_alias'].' ?/is', '\1'.$this->_config['table'].' ', $sql, 1);
            }
        }

        if ($query_with_page) { /* 带翻页的请求 */

			/*
			 * 如果外部没有传递有多少条记录, 需要先把符合此条件的记录总数查出来 
			 * 如何从外部传递总记录数进来参见 $db->queryWithPage() 函数
			 */
			if (1 > $this->page['total_rows']) {

				/* 
				 * 翻页语句为 SELECT * FROM table WHERE condition = const_variable
				 * 需要将最前面的 SELECT * FROM 替换为 SELECT COUNT(1) FROM, 后面操持不变
				 */
                if (false == ($count_sql = stristr($sql, 'from'))) {
                    $this->setLastError("[E] Parse SQL fail {$sql}");
                    return $result;
                }
                $count_sql = "SELECT COUNT(1) ".$count_sql;

				if (DATABASE_DEBUG) {
					$tmp_time  = microtime(1);
					$tmp_result= mysql_query($count_sql, $this->connection);
					$tmp_time  = microtime(1) - $tmp_time;
					$this->logDebug(sprintf("[Q] %s\n[QUERY_WITH_PAGE] consumed %.5f sec", $count_sql, $tmp_time));
				} else {
					$tmp_result= mysql_query($count_sql, $this->connection);
				}

                if (mysql_errno($this->connection)) {
                    $this->setLastError(mysql_error($this->connection));
                    return $result;
                }

                $tmp_result= mysql_fetch_row($tmp_result);
                $this->page['total_rows']  = $tmp_result[0];
            }

			/*
			 * 构造一个翻页的数组, 存放在 $this->page 数组中
			 * array(
			 *		'total_pages'	=> 10,	// 总共有 10 页
			 *		'total_rows'	=> 250, // 总共有 250 条记录
			 *		'current'		=> 2,	// 当前显示第 2 页	
			 *		'size'			=> 25,  // 每页显示 25 条记录
			 * )
			 */
            $this->page['total_pages'] = ceil($this->page['total_rows'] / $this->page['size']);

            if ($this->page['total_pages'] < $this->page['current']) {
                $this->page['current'] = $this->page['total_pages'];
            }
            if (2 > intval($this->page['current'])) {
                $this->page['current'] = 1;
            }

			/*
			 * 是否使用反向排序获取数据, 此方法可以有效减轻 db 负担
			 * 
			 * 具体方法为:
			 *
			 * 假设数据 10000 条, 每页显示 20 条, 那么有 500 页, 正常情况下显示第 450 页时我们的
			 * SQL 语句为: 
			 * SELECT * FROM table ORDER BY id DESC LIMIT ((500 - 1) * 20), 20; 
			 * 如果我们将 SQL 改为:
			 * SELECT * FROM table ORDER BY id ASC LIMIT (500 - 51), 20
			 * 那么执行效率将明显提升, 因为第一个取数据要从头开始偏移到 (500 - 1) * 20 这个位置
			 * 而第二条语句则使用ASC作为排序, 并且偏移量更小 (500 - 51) * 20
			 *
			 * 而我们下面这段代码就是干这个事情的
			 *
			 */
            $reverse_limit = false;

            if (isset($this->page['reverse_limit'])) {
                if ($this->page['reverse_limit'] < $this->page['total_rows']) {
                    $new_page = false;
                    if ($this->page['current'] == $this->page['total_pages']) {
                        $new_page = 1;
                    }  else {
                        $half = floor($this->page['total_pages'] / 2);
                        if ($half < $this->page['current']) {
                            $new_page = $this->page['total_pages'] - ($this->page['current']) + 1;
                        }
                    }
                    if ($new_page) {
                        $sql = str_replace($this->page['reverse_asc'], $this->page['reverse_desc'], $sql);
                        $sql.= ' LIMIT '.(($new_page - 1) * $this->page['size']).','.$this->page['size'];
                        $reverse_limit = true;
                    }
                }
            }

            if (!$reverse_limit) {
                $sql .= ' LIMIT '.(($this->page['current'] - 1) * $this->page['size']).','.$this->page['size'];
            }
        }

		/* 执行最终的 SQL 语句 */ 
		if (DATABASE_DEBUG) {

			$tmp_time = microtime(1);
            $result   = mysql_query($sql, $this->connection);
			$tmp_time = microtime(1) - $tmp_time;

			$this->logDebug("[Q] $sql");

            if (mysql_errno($this->connection)) {
                $this->setLastError(mysql_error($this->connection));
            } else {
                $this->affected_rows = is_resource($result) ? mysql_num_rows($result) : mysql_affected_rows($this->connection);
				$this->logDebug(sprintf("[R] %d rows in set (%.5f sec)", $this->affected_rows, $tmp_time));
			}
        } else {

			if (DATABASE_LOG_SQL_TIME > 0) {

				$tmp_time = microtime(1);
                $result   = mysql_query($sql, $this->connection);
				$tmp_time = microtime(1) - $tmp_time;

				if ($tmp_time > DATABASE_LOG_SQL_TIME) {
					$this->logError(sprintf("[Q] [%s] %s\n[R] %.5f sec consumed", date("m/d H:i:s"), $sql, $tmp_time), 'info');
                }
            } else {
                $result = mysql_query($sql, $this->connection);
			}

			if ($error_no = mysql_errno($this->connection)) {
				$this->setLastError(mysql_error($this->connection));
				$this->logError(sprintf("[Q] [%s] %s \n[E] %s(Hash=%s)\n", date("m/d H:i:s"), $sql, $this->last_error, $this->hash_str), 'error');
            } else {
                $this->affected_rows = is_resource($result) ? mysql_num_rows($result) : mysql_affected_rows($this->connection);
            }
        }

		if ($this->auto_close_mysql_link && is_resource($this->connection)) {
			mysql_close($this->connection);
			$this->connection = null;
		}

        return $result;
    }

	/*
	 * 执行语句, 并返回插入到 mysql 中的自增 Id
	 *
	 * <code>
	 *		$insert_id = $db->queryWithReturnInsertId("INSERT INTO aaa SET b = 1");
	 * </code>
	 *
	 * @param void
	 * @return integer
	 *
	 */
	function queryWithReturnInsertId($sql) {

		$old_val = $this->auto_close_mysql_link;
		$this->auto_close_mysql_link = 0;

		if ($this->query($sql) && !$this->last_error) {
			$insert_id = mysql_insert_id($this->connection);
		} else {
			$insert_id = false;
		}
		$this->auto_close_mysql_link = $old_val;

		return $insert_id;
    }

	/*
	 * 获取一行记录, 没有数据返回 null, 出错返回 false
	 *
	 * <code>
	 *		$row = $db->getRow($sql);
	 * </code>
	 *
	 * @param string $sql	要执行的 SQL
	 * @return null, false or array()
	 *
	 */
	function &getRow($sql) {
		$ret = null;
		$res = $this->query($sql);

		if ($this->affected_rows) {
			$ret = mysql_fetch_assoc($res);
		} else if (false === $res) {
			$ret = $res;
		}

		return $ret;
	}

	/*
	 * 获取多行记录, 没有数据返回 null, 出错返回 false
	 *
	 * <code>
	 *		$rows = $db->getRows($sql);
	 * </code>
	 *
	 * @param string $sql	要执行的 SQL
	 * @return null, false or array()
	 *
	 */
	function &getRows($sql) {
		$ret = array();
		$res = $this->query($sql);

		if ($this->affected_rows) {

			$ret = array();
			while ($row = mysql_fetch_assoc($res)) {
				$ret[] = $row;
			}
			mysql_free_result($res);

		} else if (false === $res) {

			$ret = $res;
		}

		return $ret;
	}

	/* 
	 * 获取某个字段的值, 没有数据返回 null, 出错返回 false
	 *
	 * <code>
	 *		$age = $db->getValue("SELECT age FROM table_user WHERE username = 'abc' LIMIT 1");
	 * </code>
	 *
	 * @param string $sql	要执行的 SQL
	 * @return null or mixed variable
	 *
	 */
	function &getValue($sql) {
		$ret = null;
		$res = $this->query($sql);

		if ($this->affected_rows) {

			$ret = mysql_fetch_row($res);
			$ret = $ret[0];
			mysql_free_result($res);

		} else if (false === $res) {

			$ret = $res;
		}

		return $ret;
	}

	/*
	 * 返回调试信息中的数据
	 *
	 * @param void
	 * @return null or array()
	 *
	 */
	function getLogs() {
		if ($this->external_logger) {
			if (method_exists($this->external_logger, 'getLogs')) {
				return $this->external_logger->getLogs();
			}
		} else {
			return $this->logs;
		}
	}

	/* 
	 * 获取上次 SQL 操作后所影响的数据行数
	 *
	 * @param void
	 * @return inteter
	 *
	 */
	function affectedRows() {
		return $this->affected_rows;
	}

	/*
	 * 获取翻页数组
	 *
	 * @param void
	 * @return array()
	 *
	 */
    function getPage() {
        return $this->page;
    }

    function close() {
        is_resource($this->connection) && mysql_close($this->connection);
		$this->connection = null;
    }

	function escape($string) {
		return get_magic_quotes_gpc()?$string:mysql_escape_string($string);
	}

    function dump($ret = false) {
        if (DATABASE_DEBUG) {
			if ($ret) {
				return $ret;
			} else {
				echo '<pre>',print_r($this, 1), '</pre>';
			}
        }
    }


	/*
	 * 记录调试信息
	 *
	 * @param string $message	信息内容
	 * @param string $level		错误级别
	 * @return void
	 *
	 */
	protected function logDebug($message, $level = LOG_PRI_DEBUG) {
		if ($message) {
			if ($this->external_logger) {
				$this->external_logger->log($message, $level);
			} else {
				$this->logs[] = $message;
			}
		}
	}

	/*
	 * 记录错误日志
	 *
	 * @param string $message	日志信息
	 * @param string $level		错误级别
	 * @return void
	 *
	 */
	protected function logError($message, $level = LOG_PRI_ERROR) {
	
		if ($message) {

			if ($this->external_logger) {
				$this->external_logger->log($message, $level);
			} else {
				error_log($message, 3, DATABASE_ERROR_LOG);
			}
		}
	}

	/*
	 * 设置错误信息
	 *
	 * @param string $msg			错误信息
	 * @return void
	 *
	 */
	protected function setLastError($message) {

		$this->last_error = $message;

		if (DATABASE_DEBUG) {
			$this->logDebug($message);
		} else {
			$this->logError($message);
		}
    }

} // end class Database

///* 
// *  DB 配置函数示例
// *
// * 下面这一行用来做开发环境，或线上环境的开关, 可以在此文件之外先定义 */
//defined("EXAMPLE_DB_DEV_SERVER") || define("EXAMPLE_DB_DEV_SERVER", true);
//
///*
// * Database class 获取某个表的所在db地址,用户名等信息的回调函数
// *
// * 关于 $table_alias 变量的说明:
// *		$table_alias 是表的别名, 我们写语句的时候可以这成这样
// *
// *		SELECT * FROM table_user WHERE account = 'oxff';
// *		当设置过 $table_alias 时, 最终到db上的请求为
// *		SELECT * FROM table_user_c8cd WHERE account = 'oxff';
// *
// *		使用别名的好处:
// *
// *		假设我们有一张表叫 table_user, 经过一段时间后我们需要更新
// *		将此表拆开, 并改名叫 table_account, 那么我们只要在这个函数
// *		内修改配置即可, 不用修改前端 SQL 语句
// *
// *		前端依然使用 table_user 当表名, 将 table_alias 改为 table_account
// *		这样 sql 语句最终被解析成
// *
// *		SELECT * FROM table_account_c8cd WHERE account = 'oxff';
// *
// * @param $const_table	下面示例语句中 case 条件, 也就是 table_alias
// * @param $hash_key		拆库的key, 通常为用户账号
// * @param $need_hash		是否需要自动生成 hash
// *
// */
//
//function example_get_db_config($const_table, $hash_key, $need_hash = true)
//{
//	/* 默认的基本信息 */
//	$host = "example.db.bierencom";
//	$user = "example";
//	$pass = "DFOISDF*D@#@E*(";
//	$port = 3306;
//	$database = $table = $table_alias = null;
//
//	/* 自动生成拆库表的hash */
//	$hash = $need_hash ? md5($hash_key) : $hash_key;
//
//	switch ($const_table) {
//
//		case "table_user":
//
//			$database	= "table_user_".substr($hash, 0, 2); // 数据库名
//			$table		= "table_user_".substr($hash, 0, 4); // // 数据库中真实的表名
//			$table_alias= "table_user"; // 表别名, sql 语句中使用此别名当表名
//
//			break;
//
//		case "table_user_fav":
//
//			if ($need_hash) { 
//				$dec2 = hexdec(substr($hash, 0, 2)); // 前端没传递hash进来, 自动拆分
//			} else {
//				/*
//				 *  反端cron脚本直接传递hash进来, 直接使用
//				 *
//				 *  对应的语句为 $db->load("table_user_fav", 00, false);
//				 */
//				$dec2 = $hash; 
//			}
//			if (32 > $dec2)			{ $host = "example1.db.bierencom"; $port = 3306; // 拆db地址, 不同的ip, port
//			} else if (64  > $dec2)	{ $host = "example2.db.bierencom"; $port = 3307; // 拆db地址, 不同的ip, port
//			} else if (127 > $dec2) { $host = "example3.db.bierencom"; $port = 3308; // 拆db地址, 不同的ip, port
//			} else if (256 > $desc) { $host = "example4.db.bierencom"; $port = 3309; // 拆db地址, 不同的ip, port
//			}
//			$database	= "table_user_fave_".substr($hash, 0, 2); // 数据库名
//			$table		= "table_user_fave_".substr($hash, 0, 4); // 数据库中真实的表名
//			$table_alias= "table_user"; // 表别名, sql 语句中使用此别名当表名
//
//			break;
//
//		default:
//			return trigger_error("undefined table: ".$table, E_USER_ERROR);
//	}
//
//	if (EXAMPLE_DB_DEV_SERVER) {
//		/* 通常我们开发服务器都在一台机器上, 但表结构还是不变的, 所以我们在这个地方 hook 掉地址 */
//		$host = "127.0.0.1";
//		$user = "root";
//		$pass = "";
//		$port = 3306;
//	}
//
//	/* 最终返回的信息, 以下字段为必须返回的 */
//	return array(
//		'host'		=> $host.($port ? ":$port" : ""),
//		'user'		=> $user,
//		'pass'		=> $pass,
//		'port'		=> $port,
//		'database'	=> $database,
//		'table'		=> $table,
//		'table_alias'=>$table_alias
//	);
//}

?>
